package SSF.OS.NetFlow;

import SSF.OS.*;
import SSF.OS.TCP.*;
import SSF.Net.Util.*;
import java.io.*;


// IpNetFlow
//
// March 7, 2001. Add information about "external/internal NIC peer" and other
//    information about NHI - IP transfer.
// Feb 12, 2001. Add "ALL_PROTOCOLS" static variable to IpFlowCollector.
// 
// This is the version "nextHop" field is added.
//
// The "src_mask" and "dst_mask" here are the masks for SUFFIX, while for
// Cisco NetFlow, they are prefix masks.
//
// This is the version that has support to "nextHop", it also checks the
// "output" NIC to when deciding whether two flows are the same.
//
// An extensio of NetFlow.


/* Class of the ipnetflow data.
 * It's NOT fully compliant with cisco netflow version 5.0
 * now the resolution only supports as low as "host" level,
 * that is, port number is not considered. To take the port
 * number into account, sub-class it and add your own fields, 
 * notice that many other small changes are needed as well.
 */
public class IpNetFlow extends NetFlow
{
    /** IP of the next Hop.
     Newly added, in the standard IpHeader, this info is not available.
     In cisco ver7.0, this field is always set to be 0*/
    public int nextHop = 0;

    /** NHI address of the collector, not including the interface ID */
    public String nhi = null;

    /** Source IP (prefix) address */
    public int srcAddr = 0;

    /** Destinatio IP (prefix) address */
    public int dstAddr = 0;

    /** index of the input NIC */
    public short input = 0;

    /** whether the input NIC is an external NIC*/
    //public boolean externalInput = false;

    /** index of the output NIC */
    public short output = 0;

    /** whether the output NIC is an exteran NIC*/
    //public boolean exteranlOutput = false;

    /** number of packets in the flow */
    public int dPkts = 0;

    /** size of data in the flow 
        This is not completely the same as in cisco */
    public int dOctets = 0;

    /** start time */
    public float First = 0;

    /** the last time a packet of this flow arrives */
    public float Last = 0;

    /** cumulative OR of TCP flags */
    public byte tcp_flags = 0;

    /** IP protocol type */
    public byte protocol = 0;

    /** source IP suffix mask */
    public byte src_mask = (byte)0xff;

    /** destination IP suffix mask */
    public byte dst_mask = (byte)0xff;

    /**
     * default constructor
     */
    public IpNetFlow() {};

    /** construct a new flow from an ip packet
     * @param ipPacket the packet received
     * @param inID the ID of the input NIC
     * @param outID the ID of the output NIC 
     * @param curTime currentTime
     * @param nhiAddr the NHI address of the IpFlowCollector
     * @param srcMask
     * @param dstMask
     */
    public IpNetFlow(IpHeader ipPacket, short inID, short outID,
		     float curTime, String nhiAddr, 
		     byte srcMask, byte dstMask) 
    {
	//-- source and dest address mask
	src_mask = srcMask;
	dst_mask = dstMask;

	//-- source and destination Address
        srcAddr = ipPacket.SOURCE_IP & ((int)src_mask | 0xffffff00);
	dstAddr = ipPacket.DEST_IP & ((int)dst_mask | 0xffffff00);

	//-- collector NHI address
	nhi = nhiAddr;

	//-- input & output ID of NICs
	input = inID;
	output = outID;

	//-- the nextHop
	nextHop = ipPacket.NEXT_HOP_IP;

	//-- number of packets and data size in the flow
	dPkts = 1;

	//-- 20 is the size of the ip header.
	dOctets = ipPacket.bytecount() - 20;

	//-- set starting time (first)
	First = curTime;

	//-- initial last time
	Last = First;

	//-- ???protocol NO.         need it???????????
	protocol = (byte)(ipPacket.PROTOCOL_NO);

	if (ipPacket.PROTOCOL_NO == Protocols.TCP_PRTL_NUM)
	    tcp_flags = ((TCP_Header)(ipPacket.payload())).flags;

	next = null;
    }

    /**
     * Generate a byte array from this record. Notice that the nhi address is
     * stored at the end, that is, starting from index 36.
     * @ret an byte array consists of the data of this record
     */
    public byte[] dumpToBytes()
    {
	int len = recordSize();
	byte[] bytes = new byte[len];
	int index = 0;

	index = BytesUtil.intToBytes(srcAddr, bytes, index);
	index = BytesUtil.intToBytes(dstAddr, bytes, index);
	index = BytesUtil.shortToBytes(input, bytes, index);
	index = BytesUtil.shortToBytes(output, bytes, index);
	index = BytesUtil.intToBytes(nextHop, bytes, index); 
	index = BytesUtil.intToBytes(dPkts, bytes, index);
	index = BytesUtil.intToBytes(dOctets, bytes, index);
	index = BytesUtil.floatToBytes(First, bytes, index);
	index = BytesUtil.floatToBytes(Last, bytes, index);
	bytes[index++] = tcp_flags;
	bytes[index++] = protocol;
	bytes[index++] = src_mask;
	bytes[index++] = dst_mask;
	BytesUtil.stringToBytes(nhi, bytes, index);

	return bytes;
    }   

    /**
     * extract the record information from a byte array
     * @param bytes the byte array that stores the data
     * @param len the length of the data stored in the array (Not the length of the array)
     */
    public void readFromBytes(byte[] bytes, int len)
    {
	srcAddr = BytesUtil.bytesToInt(bytes, 0);
	dstAddr = BytesUtil.bytesToInt(bytes, 4);
	input   = BytesUtil.bytesToShort(bytes, 8);
	output  = BytesUtil.bytesToShort(bytes, 10);
	nextHop = BytesUtil.bytesToInt(bytes,12);
	dPkts   = BytesUtil.bytesToInt(bytes, 16);
	dOctets = BytesUtil.bytesToInt(bytes, 20);
	First   = BytesUtil.bytesToFloat(bytes, 24);
	Last    = BytesUtil.bytesToFloat(bytes, 28);
	tcp_flags = bytes[32];
	protocol  = bytes[33];
	src_mask  = bytes[34];
	dst_mask  = bytes[35];

	//read nhi, bytes[36] stores the length of the nhi address.
	nhi = new String(bytes, 37, len-37);
    }


    /** Check whether the given packet belongs to this flow.
     * <BR>
     * NOTICE that here it doesn't not check input or output NIC ID because they
     * are not in the IpHeader. If they matter, check it in other parts of your
     * code as did in IpFlowCollector.
     * @param packet the packet to be checked.
     */
    public boolean acceptPacket(ProtocolMessage packet)
    {
	//-- in the initial version, only the sourceIP, destinationIP
	//-- and Protocol type (No.) are checked. Ports are NOT considered

	IpHeader ipPacket = (IpHeader)packet;

	if (ipPacket.PROTOCOL_NO != protocol)
	    return false;            // not the protocol interested
	if ((ipPacket.SOURCE_IP & ((int)src_mask | 0xffffff00)) != srcAddr)
	    return false;            // source IP don't match
	if ((ipPacket.DEST_IP & ((int)dst_mask | 0xffffff00)) != dstAddr)
	    return false;            // dest IP don't match
	return true;                 // it belongs to this flow
    }

    /**
     * The "next" field is NOT included when count the size of the record.
     * The record consists of
     * <UL>
     * <LI>nhi        String, size NOT FIXED
     * <LI>srcAddr    int     4 bytes
     * <LI>dstAddr    int     4 bytes
     * <LI>input      short   2 bytes
     * <LI>output     short   2 bytes
     * <LI>nextHop    int     4 bytes   newly added
     * <LI>dPkts      int     4 bytes
     * <LI>dOctets    int     4 bytes
     * <LI>First      float   4 bytes
     * <LI>Last       float   4 bytes
     * <LI>tcp_flags  byte    1 byte
     * <LI>protocol   byte    1 byte
     * <LI>src_mask   byte    1 byte
     * <LI>dst_mask   byte    1 byte
     * </UL>
     * @ret  return the size in bytes of an instance ("next" field excluded).
     */
    public int recordSize()
    {
	return nhi.length() + 36 + 1; //the 1 is the byte recording the length of nhi address.
    }

    /** update the flow for the given packet
     * @param packet given packet
     * @param inID index of the incoming NIC
     * @param outID index of the outgoing NIC
     * @param curTime the current time
     */
    public void update(ProtocolMessage packet, float curTime)
    {
	IpHeader ipPacket = (IpHeader)packet;

	//-- update number of packets and data size
	dPkts++;

	//-- 20 is the size of IP header
	dOctets += (ipPacket.bytecount() - 20);

	//-- update "last time receive packet in this flow"
	Last = curTime;

	//-- update the tcp_flag
	if (ipPacket.PROTOCOL_NO == Protocols.TCP_PRTL_NUM)
	    tcp_flags |= ((TCP_Header)(ipPacket.payload())).flags;
    }

    /**
     * make the key needed for IpNetFlow from an ip Packet. The key is based
     * on the srcAddr and dstAddr
     */
    public static Long makeKey(IpHeader ipPacket)
    {
	long key = (long)ipPacket.SOURCE_IP;
	key = key << 32;
	key |= ipPacket.DEST_IP;
	return (new Long(key));
    }

    /**
     * Construct a key for this flow based on srcAddr and dstAddr
     * key is (srcAddr << 32)+dstAddr
     * <BR>
     * This is used in for IpNetFlow.
     * <BR>
     * Port number are not considered here. So if you want to construct
     * the key in a different way and use it for a different flow, overwrite 
     * this function, and remember to change the static makeKey(ipPacket)
     * as well.
     */
    public  Long makeKey()
    {
	long key = (long)srcAddr;
	key = key << 32;
	key |= dstAddr;
	return (new Long(key));
    }


    /**
     * dump to String
     */
    public String toString()
    {
	return (IP_s.IPtoString(srcAddr) + "\t" + IP_s.IPtoString(dstAddr) 
		+ "\t" + nhi + "\t" + input + "\t"
		+ output + "\t" + IP_s.IPtoString(nextHop)
		+"\t" + dPkts + "\t" + dOctets + "\t"
		+ First + "\t" + Last + "\t" 
		+ Integer.toHexString((int)tcp_flags & 0xff) + "\t"
		+ ((protocol==IpFlowCollector.ALL_PROTOCOLS)?"all":Protocols.getProtocolByNumber((int)protocol))
		+ "\t" + Integer.toHexString((int)src_mask & 0xff) 
		+ "\t" + Integer.toHexString((int)dst_mask & 0xff));
    }

    /**
     * Dump the flow to a stream.
     * If this is a list, dump the whole list.
     * <BR>
     * NOTICE: this is used before the function dumpToBytes is written.
     * It's obsolete after that.
     * @param outStream the outputStream into which the data is dumped.
     */
    public void dumpBinaryOut(DataOutputStream outStream)
    {
	try {
	    outStream.writeInt(srcAddr);
	    outStream.writeInt(dstAddr);
	    outStream.writeByte(input);
	    outStream.writeByte(output);
	    outStream.writeInt(nextHop);
	    outStream.writeInt(dPkts);
	    outStream.writeInt(dOctets);
	    outStream.writeFloat(First);
	    outStream.writeFloat(Last);
	    outStream.writeByte(tcp_flags);
	    outStream.writeByte(protocol);
	    outStream.writeByte(src_mask);
	    outStream.writeByte(dst_mask);
	    outStream.writeUTF(nhi);

	    //need for debuggin purpose
	    //System.out.println(this.toString());
	}
	catch (IOException e) {
	    System.err.println("I/O error in NetFlow.dumpBinaryOut");
	    System.err.println(e.getMessage());
	    System.exit(1);
	}	    
	
	if (next != null)
	    next.dumpBinaryOut(outStream);
    }

    /**
     * Read data from a binary stream for a given flow.
     * <BR>
     * NOTICE: this is used before the function readFromBytes is written.
     * It's obsolete after that.
     */
    public void readBinaryIn(DataInputStream inStream)
    {
	try {
	    srcAddr = inStream.readInt();
	    dstAddr = inStream.readInt();
	    input = inStream.readByte();
	    output = inStream.readByte();
	    nextHop = inStream.readInt();
	    dPkts = inStream.readInt();
	    dOctets = inStream.readInt();
	    First = inStream.readFloat();
	    Last = inStream.readFloat();
	    tcp_flags = inStream.readByte();
	    protocol = inStream.readByte();
	    src_mask = inStream.readByte();
	    dst_mask = inStream.readByte();
	    nhi = inStream.readUTF();
	}
	catch (IOException e) {
	    System.err.println("I/O error in NetFlow.readBinaryIn");
	    System.err.println(e.getMessage());
	    System.exit(1);
	}
    }

}











